package com.gitee.chili.leisure;

import org.springframework.util.Assert;

import java.util.function.Supplier;

/**
 *
 * 单例工厂，用于缓存单例结果并从{@link #get()}（可空）和{@link #obtain()}（可安全使用）中获取.
 * <p>
 * {@link SingletonFactory}可以通过{@code of}工厂方法或通过提供默认工厂实例作为后备的构造函数来构造。
 * 对于方法引用提供者来说特别有用，对于返回{@code null}并缓存结果的方法来说，它可以使用默认的形式。
 *
 * @author week solider
 * @version v1.0
 * @since 2019/12/24
 *
 * @see Supplier
 */
public class SingletonFactory<T> implements Supplier<T> {

    private T currentInstance;

    private Supplier<? extends T> factory;

    /**
     * 获取当前实例，可为 {@code NULL} 首先会查找是否创建，如为{@code false}
     * 则会查找工厂中是否创建，如为{@code false}，则返回{@code NULL}
     *
     * @return this 当前实例
     */
    public T get() {
        if (currentInstance == null) {
            synchronized (this) {
                if (currentInstance == null) {
                    if (factory != null) {
                        currentInstance = factory.get();
                    }
                }
            }
        }
        return currentInstance;
    }

    /**
     * 通过 {@link #get} 查找实例是否存在，不存在则抛出异常
     *
     * @return T 当前实例
     * @exception IllegalArgumentException this == null
     */
    public T obtain(){
        T instance = get();
        Assert.notNull(instance,"No instance from factory");
        return instance;
    }

    /**
     * 构造一个单例工厂，对外提供的一个静态方法，方便调用者直接构造工厂
     * 但前提是必须要一个需要创建的单例{@code bean}
     * @param instant 需要缓存的bean
     * @param <T> 声明为泛型
     * @return this
     */
    public static <T> SingletonFactory<T> of(T instant) {
        return new SingletonFactory<>(instant);
    }

    /**
     * 与{@link #of(Object)}相同，本方法提供了lambda表达式特性
     * @param factory lambda
     * @param <T> 声明为泛型
     * @return this
     */
    public static <T> SingletonFactory<T> of(Supplier<T> factory) {
        return new SingletonFactory<>(factory);
    }

    //====================================== 构造函数 =========================================

    public SingletonFactory(T instance, Supplier<? extends T> factory) {
        this.currentInstance = instance;
        this.factory = factory;
    }

    private SingletonFactory(Supplier<? extends T> factory) {
        this.factory = factory;
    }

    private SingletonFactory(T instance) {
        this.currentInstance = instance;
    }

}
